package ru.ifmo.common.xml;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import ru.ifmo.common.ObjectExtractor;
import ru.ifmo.common.Source;

import java.util.Map;

/**
 * @author avhaliullin
 */
public class XmlMapExtractor<K, V> implements XmlObjectExtractor<Map<K, V>> {
    private final String tagMapEntry;
    private final Source<Map<K, V>> mapSource;
    private final String nodeKey;
    private String nodeValue;
    private XmlObjectExtractor<V> valueTagExtractor;
    private ObjectExtractor<V, String> valueAttributeExtractor;
    private ObjectExtractor<K, String> keyAttributeExtractor;
    private XmlObjectExtractor<K> keyTagExtractor;

    private XmlMapExtractor(Source<Map<K, V>> mapSource, String tagMapEntry, String nodeKey) {
        this.nodeKey = nodeKey;
        this.mapSource = mapSource;
        this.tagMapEntry = tagMapEntry;
    }


    public XmlMapExtractor(Source<Map<K, V>> mapSource, String tagMapEntry, String attributeKey, ObjectExtractor<K, String> keyExtractor, XmlObjectExtractor<V> valueExtractor) {
        this(mapSource, tagMapEntry, attributeKey);
        this.valueTagExtractor = valueExtractor;
        this.keyAttributeExtractor = keyExtractor;
    }

    public XmlMapExtractor(Source<Map<K, V>> mapSource, String tagMapEntry, String tagKey, XmlObjectExtractor<K> keyExtractor, String tagValue, XmlObjectExtractor<V> valueTagExtractor) {
        this(mapSource, tagMapEntry, tagKey);
        this.valueTagExtractor = valueTagExtractor;
        this.keyTagExtractor = keyExtractor;
        this.nodeValue = tagValue;
    }

    public XmlMapExtractor(Source<Map<K, V>> mapSource, String tagMapEntry, String attributeKey, ObjectExtractor<K, String> keyExtractor, String attributeValue, ObjectExtractor<V, String> valueExtractor) {
        this(mapSource, tagMapEntry, attributeKey);
        this.valueAttributeExtractor = valueExtractor;
        this.keyAttributeExtractor = keyExtractor;
        this.nodeValue = attributeValue;
    }

    public Map<K, V> extract(Element source) {
        Map<K, V> mapInstance = mapSource.create();
        NodeList statesList = source.getElementsByTagName(tagMapEntry);
        for (Object node : XmlUtil.getNodeListIterable(statesList)) {
            Element mapEntry;
            if (node instanceof Element) {
                mapEntry = (Element) node;

                V value;
                K key;

                if (valueAttributeExtractor != null) {
                    value = valueAttributeExtractor.extract(mapEntry.getAttribute(nodeValue));
                } else {
                    if (nodeValue != null) {
                        value = valueTagExtractor.extract(XmlUtil.getSingleElement(mapEntry, nodeValue));
                    } else {
                        value = valueTagExtractor.extract(mapEntry);
                    }
                }

                if (keyAttributeExtractor != null) {
                    key = keyAttributeExtractor.extract(mapEntry.getAttribute(nodeKey));
                } else {
                    key = keyTagExtractor.extract(XmlUtil.getSingleElement(mapEntry, nodeKey));
                }
                mapInstance.put(key, value);
            }
        }
        return mapInstance;
    }
}
